/*
 * @(#)PageNavigatorDirective.java $version 2010. 8. 11
 *
 * Copyright 2007-2010 NHN Corp. All rights Reserved. 
 * NHN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
package com.nhncorp.usf.core.result.template.directive;

import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import com.nhncorp.usf.core.Constants;
import com.nhncorp.usf.core.config.PagerConfigInfo;
import com.nhncorp.usf.core.config.PagerConfigInfos;
import com.nhncorp.usf.core.util.NumberUtils;
import com.nhncorp.usf.core.util.StringUtil;
import com.nhncorp.usf.web.taglibs.pager.PagingElement;

import freemarker.core.Environment;
import freemarker.ext.beans.BeansWrapper;
import freemarker.template.SimpleScalar;
import freemarker.template.SimpleSequence;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;

/**
 * Page 이동 버튼을 생성하는 {@link TemplateDirectiveModel}
 *
 * @author Web Platform Development Team
 */
public class PageNavigatorDirective extends AbstractTemplateDirectiveModel {
    public static final String DEFAULT_TEMPLATE = "defaultNavigator.ftl";
    public static final String PAGING_ELEMENT = "pagingElements";
    public static final String PAGE_INFO = "pagerInfo";
    public static final String PAGER_CONFIG = "config";
    public static final String REQUEST = "request";
    public static final String REQUEST_URI = "requestUri";
    public static final String QUERY_STRING = "queryString";
    public static final String TEMPLATE_NAME = "templateName";
    public static final String PAGE_PARAMETER_NAME = "pageParam";

    /**
     * @param env      Environment
     * @param params   Map
     * @param loopVars TemplateModel
     * @param body     TemplateDirectiveBody
     * @throws TemplateException the TemplateException
     * @throws IOException       the IOException
     * @see freemarker.template.TemplateDirectiveModel#execute(freemarker.core.Environment, java.util.Map, freemarker.template.TemplateModel[], freemarker.template.TemplateDirectiveBody)
     */
    @SuppressWarnings("unchecked")
	public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) throws TemplateException, IOException {
        String type = getAsString(params.get("type"));
        String templateName = getAsString(params.get("template"));
        HttpServletRequest request = (HttpServletRequest)getAsExtObject(env.getVariable("Request"),
                                                                        HttpServletRequest.class);
        type = type == null ? "default" : type;

        log.debug("type : " + type + ", template : " + templateName);

        // get directive parameters

        String pageParamName = getAsString(params.get("pageParam"));

        if (pageParamName == null) {
            pageParamName = Constants.DEFAULT_PAGE_PARAM;
        }

        int page = NumberUtils.toInt(request.getParameter(pageParamName), 1);

        String queryString = StringUtil.defaultString(request.getQueryString());
        String requestUri = request.getRequestURI();
        String requestMethod = request.getMethod();
        Integer pageSize = getAsInteger(params.get("pageSize"));
        Integer indexSize = getAsInteger(params.get("indexSize"));
        Integer totalRow = getAsInteger(params.get("totalRow"));

        if (totalRow <= 0)//totalRow 0이면 page가 "1 | 0" 으로 출력되는 버그를 없애기 위해서 , 이코드를 추가함.
        {
            totalRow = 1;
        }

        PagerConfigInfo pagerConfigInfo = PagerConfigInfos.get(type);
        pagerConfigInfo = pagerConfigInfo == null ? new PagerConfigInfo() : pagerConfigInfo;
        env.setVariable(PAGER_CONFIG, BeansWrapper.getDefaultInstance().wrap(pagerConfigInfo));

        PagerInfo pagerInfo = new PagerInfo(page, requestUri, queryString, requestMethod);
        pagerInfo.init(type, totalRow, pageSize, indexSize);
        requestUri = requestUri.endsWith("?") ? requestUri.substring(0, requestUri.length() - 1) : requestUri;

        if (templateName == null && pagerConfigInfo == null) {
            throw new IllegalArgumentException("template name is null.");
        }

        templateName = StringUtil.isEmpty(templateName) ? pagerConfigInfo.getTemplate() : templateName;

        env.setVariable(PAGE_INFO, BeansWrapper.getDefaultInstance().wrap(pagerInfo));
        env.setVariable(REQUEST_URI, new SimpleScalar(requestUri));
        env.setVariable(QUERY_STRING, new SimpleScalar(removePageParam(queryString, pageParamName)));
        env.setVariable(TEMPLATE_NAME, new SimpleScalar(templateName));
        env.setVariable(PAGE_PARAMETER_NAME, new SimpleScalar(pageParamName));

        String useTemplate = getTemplateName(templateName);
        String contentStr = null;

        if (body != null) {
            StringWriter bodyWriter = new StringWriter();
            body.render(bodyWriter);
            contentStr = bodyWriter.toString();
        }

        log.debug("body content : " + contentStr);

        if (contentStr == null || contentStr.trim().length() == 0) {
            env.setVariable(PAGING_ELEMENT, new SimpleSequence(getPagingElements("{first}{prev}{index}{next}{last}")));
        } else {
            env.setVariable(PAGING_ELEMENT, new SimpleSequence(getPagingElements(contentStr)));
        }

        env.include(env.getConfiguration().getTemplate(useTemplate));
    }

    /**
     * Removes the page param.
     *
     * @param inQueryString the in query string
     * @param pageParam     the page param
     * @return the string
     */
    protected String removePageParam(String inQueryString, String pageParam) {
        String queryString = inQueryString;

        if (queryString.startsWith(pageParam + "=")) {
            queryString = queryString.replaceFirst(pageParam + "=[0-9]*", "");
        } else if (queryString.indexOf("&" + pageParam + "=") >= 0) {
            queryString = queryString.replaceFirst("&" + pageParam + "=[0-9]*", "");
        }

        return queryString;
    }

    /**
     * 입력받은 정보를 통해 TemplateName을 정한다. fixed과 float는 미리 정의된 템플릿의 예약어로 사용.
     *
     * @param templateName String
     * @return String
     */
    private String getTemplateName(String templateName) {
        if (StringUtil.isEmpty(templateName) || templateName.equalsIgnoreCase(Constants.PAGER_TYPE_FIXED)
                || templateName.equalsIgnoreCase(Constants.PAGER_TYPE_FLOAT)) {
            return DEFAULT_TEMPLATE;
        }

        return templateName;
    }

    /**
     * Pattern String을 파싱하여 출력하고자 하는 구성 요소를 정보화.
     *
     * @param pattern 어떠한 구성요소를 포함 시킬지 나타내는 패턴 문자열
     * @return 문자열을 파싱하여 생성된 {@link PagingElement} {@link List}
     */
    private List<PagingElement> getPagingElements(String pattern) {
        List<PagingElement> pagingElements = new ArrayList<PagingElement>();
        boolean readNext = true;
        int openPos = -1;
        int closePos = -1;
        PagingElement element = null;
        String lPattern = pattern;

        while (readNext) {
            openPos = lPattern.indexOf("{");
            closePos = lPattern.indexOf("}");

            if (openPos > -1 && closePos > -1) {
                element = new PagingElement();
                element.setBefore(lPattern.substring(0, openPos));
                parseElement(element, lPattern.substring(openPos + 1, closePos));
                pagingElements.add(element);
                lPattern = lPattern.substring(closePos + 1);
                readNext = true;
            } else {
                readNext = false;
            }
        }

        if (element != null) {
            element.setAfter(lPattern);
        }

        return pagingElements;
    }

    /**
     * 입력된 elementType과 property 문자열을 파싱하여 {@link PagingElement} 인스턴스에 값을 할당.
     * 문자열은 다음과 같은 포맷을 가져야 한다.
     * index:seperator=* (elementType:property name=property value)
     *
     * @param element    {@link PagingElement} 문자열을 파싱하여 값을 할당 할 대상
     * @param patternStr 입력된 문자열.
     */
    private void parseElement(PagingElement element, String patternStr) {
        String lPatternStr = patternStr;
        int colon = lPatternStr.indexOf(":");

        if (colon == -1) {
            element.setElementType(lPatternStr);
            return;
        }

        element.setElementType(lPatternStr.substring(0, colon));
        lPatternStr = lPatternStr.substring(colon + 1);

        Map<String, String> properties = new HashMap<String, String>();
        int equalSign = lPatternStr.indexOf("=");
        int comma = lPatternStr.indexOf(",");
        String name = "";
        String value = "";

        while (equalSign > -1) {
            String propertyStr = comma > -1 ? lPatternStr.substring(0, comma) : lPatternStr;
            name = propertyStr.substring(0, equalSign);
            value = comma > -1 ? propertyStr.substring(equalSign + 1, comma) : propertyStr.substring(equalSign + 1);
            properties.put(name.trim(), value.trim());

            if (comma == -1) {
                break;
            }

            lPatternStr = lPatternStr.substring(comma + 1);
            equalSign = lPatternStr.indexOf("=");
            comma = lPatternStr.indexOf(",");
        }

        element.setProperties(properties);
    }
}
